eval 函数const jsStr = `
var innerDataVar = 'innerDataVar'
const innerDataConst = 'innerDataConst'
console.log('[Inner]:', globalData)
console.log('[Inner]:', localData)
`
const globalData = 'globalData'
function test() {
const localData = 'localData'
// (1) eval
eval(jsStr)
console.log('[Outer]:', innerDataVar)
console.log('[Outer]:', innerDataConst)
}
test()
// [Inner]: globalData
// [Inner]: localData
// [Outer]: innerDataVar
// Uncaught ReferenceError: innerDataConst is not defined
eval 会开辟一个块级作用域with 语句const someObject = {
someProperty: 'Hello, world!'
}
with (someObject) {
eval('console.log(someProperty)')
}
// Hello, world!
在这个例子中,with 语句更改了 eval 中代码的作用域,使其可以直接访问 someObject 的属性
<script> 元素const jsStr = `
var innerDataVar = 'innerDataVar'
const innerDataConst = 'innerDataConst'
console.log('[Inner]:', globalData)
console.log('[Inner]:', localData)
`
const globalData = 'globalData'
function test() {
const localData = 'localData'
// (2) <script> 元素
// 创建一个新的 <script> 元素
const script = document.createElement('script')
script.type = 'text/javascript'
// 将 JS 字符串设置为 script 元素的 textContent
script.textContent = jsStr
// 将 script 元素添加到页面的 head 或 body 中
document.head.appendChild(script)
console.log('[Outer]:', innerDataVar)
console.log('[Outer]:', innerDataConst)
}
test()
// [Inner]: globalData
// Uncaught ReferenceError: localData is not defined
// [Outer]: innerDataVar
// [Outer]: innerDataConst
Webpack 动态加载的原理就是这个方案
<script> 添加到 DOM 中。上述代码就是同步的new Function() 构造函数const jsStr = `
var innerDataVar = 'innerDataVar'
const innerDataConst = 'innerDataConst'
console.log('[arguments]:', arguments)
console.log('[Inner]:', globalData)
console.log('[Inner]:', localData)
return 'result'
`
const globalData = 'globalData'
function test() {
const localData = 'localData'
// (3) new Function()
// 使用 new Function() 来创建一个新的函数
const func = new Function(jsStr)
// 调用这个新创建的函数
const result = func(1, 2)
console.log('[Outer]:', innerDataVar)
console.log('[Outer]:', innerDataConst)
console.log('[result]:', result)
}
test()
// [arguments]: Arguments(2) [1, 2, length: 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// [Inner]: globalData
// Uncaught ReferenceError: localData is not defined
// Uncaught ReferenceError: innerDataVar is not defined
// Uncaught ReferenceError: innerDataConst is not defined
// [result]: result
new Function() 创建的函数不会继承其创建时的函数作用域setTimeout / setInterval 函数const jsStr = `
var innerDataVar = 'innerDataVar'
const innerDataConst = 'innerDataConst'
console.log('[Inner]:', globalData)
console.log('[Inner]:', localData)
`
const globalData = 'globalData'
function test() {
const localData = 'localData'
// (4) setTimeout / setInterval
setTimeout(jsStr, 0)
console.log('[Outer]:', innerDataVar)
console.log('[Outer]:', innerDataConst)
setTimeout(() => {
console.log('[setTimeout]:', innerDataVar)
console.log('[setTimeout]:', innerDataConst)
}, 1000)
}
test()
// Uncaught ReferenceError: innerDataVar is not defined
// Uncaught ReferenceError: innerDataConst is not defined
// [Inner]: globalData
// Uncaught ReferenceError: localData is not defined
// [setTimeout]: innerDataVar
// [setTimeout]: innerDataConst
// 创建一个可执行的 JS 代码字符串
const jsStr = `
self.onmessage = function(e) {
var result = e.data[0] + e.data[1];
self.postMessage(result);
self.close();
}`
// 创建一个 Blob 对象来作为 worker 的源
const blob = new Blob([jsStr], { type: 'application/javascript' })
// 根据 Blob 对象创建一个 object URL,并创建 worker
const worker = new Worker(URL.createObjectURL(blob))
// 设置 worker 的消息回调
worker.onmessage = function (e) {
console.log('Result: ' + e.data)
}
// 向 worker 发送数据
worker.postMessage([1, 2])
console.log('[主线程]')
// [主线程]
// Result: 3
动态运行字符串的方案都会有安全风险,这些风险主要有以下两个因素引起:
性能问题:
如果有场景必须要使用动态运行 JS 字符串的情况下,应优先考虑动态创建 <script> 标签、new Function() 和 Web Worker
<script> 标签:这种方式相对安全,可以用于加载外部的 JS 代码,适用于需要动态加载并立即执行脚本的场景,但要尽量避免使用外部不可信的源new Function() 提供了较好的安全性和灵活性,适用于需要动态执行但不需要访问外部局部变量的场景